/******************************************************************************* * Copyright (c) 2015 Jeff Martin. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ package cuchaz.enigma.analysis; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.TreeMap; import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.strobel.decompiler.languages.Region; import com.strobel.decompiler.languages.java.ast.AstNode; import com.strobel.decompiler.languages.java.ast.Identifier; import cuchaz.enigma.mapping.Entry; public class SourceIndex { private String m_source; private TreeMap<Token, EntryReference<Entry, Entry>> m_tokenToReference; private Multimap<EntryReference<Entry, Entry>, Token> m_referenceToTokens; private Map<Entry, Token> m_declarationToToken; private List<Integer> m_lineOffsets; private boolean m_ignoreBadTokens; public SourceIndex(String source) { this(source, true); } public SourceIndex(String source, boolean ignoreBadTokens) { m_source = source; m_ignoreBadTokens = ignoreBadTokens; m_tokenToReference = Maps.newTreeMap(); m_referenceToTokens = HashMultimap.create(); m_declarationToToken = Maps.newHashMap(); m_lineOffsets = Lists.newArrayList(); // count the lines m_lineOffsets.add(0); for(int i = 0; i < source.length(); i++) if(source.charAt(i) == '\n') m_lineOffsets.add(i + 1); } public String getSource() { return m_source; } public Token getToken(AstNode node) { // get the text of the node String name = ""; if(node instanceof Identifier) name = ((Identifier)node).getName(); // get a token for this node's region Region region = node.getRegion(); if(region.getBeginLine() == 0 || region.getEndLine() == 0) { // DEBUG System.err.println(String.format( "WARNING: %s \"%s\" has invalid region: %s", node.getNodeType(), name, region)); return null; } Token token = new Token(toPos(region.getBeginLine(), region.getBeginColumn()), toPos(region.getEndLine(), region.getEndColumn()), m_source); if(token.start == 0) { // DEBUG System.err.println(String.format( "WARNING: %s \"%s\" has invalid start: %s", node.getNodeType(), name, region)); return null; } // DEBUG // System.out.println( String.format( "%s \"%s\" region: %s", // node.getNodeType(), name, region ) ); // if the token has a $ in it, something's wrong. Ignore this token if(name.lastIndexOf('$') >= 0 && m_ignoreBadTokens) { // DEBUG System.err.println(String.format( "WARNING: %s \"%s\" is probably a bad token. It was ignored", node.getNodeType(), name)); return null; } return token; } public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) { Token token = getToken(node); if(token != null) { EntryReference<Entry, Entry> deobfReference = new EntryReference<Entry, Entry>(deobfEntry, token.text, deobfContext); m_tokenToReference.put(token, deobfReference); m_referenceToTokens.put(deobfReference, token); } } public void addDeclaration(AstNode node, Entry deobfEntry) { Token token = getToken(node); if(token != null) { EntryReference<Entry, Entry> reference = new EntryReference<Entry, Entry>(deobfEntry, token.text); m_tokenToReference.put(token, reference); m_referenceToTokens.put(reference, token); m_declarationToToken.put(deobfEntry, token); } } public Token getReferenceToken(int pos) { Token token = m_tokenToReference.floorKey(new Token(pos, pos, null)); if(token != null && token.contains(pos)) return token; return null; } public Collection<Token> getReferenceTokens( EntryReference<Entry, Entry> deobfReference) { return m_referenceToTokens.get(deobfReference); } public EntryReference<Entry, Entry> getDeobfReference(Token token) { if(token == null) return null; return m_tokenToReference.get(token); } public void replaceDeobfReference(Token token, EntryReference<Entry, Entry> newDeobfReference) { EntryReference<Entry, Entry> oldDeobfReference = m_tokenToReference.get(token); m_tokenToReference.put(token, newDeobfReference); Collection<Token> tokens = m_referenceToTokens.get(oldDeobfReference); m_referenceToTokens.removeAll(oldDeobfReference); m_referenceToTokens.putAll(newDeobfReference, tokens); } public Iterable<Token> referenceTokens() { return m_tokenToReference.keySet(); } public Iterable<Token> declarationTokens() { return m_declarationToToken.values(); } public Iterable<Entry> declarations() { return m_declarationToToken.keySet(); } public Token getDeclarationToken(Entry deobfEntry) { return m_declarationToToken.get(deobfEntry); } public int getLineNumber(int pos) { // line number is 1-based int line = 0; for(Integer offset : m_lineOffsets) { if(offset > pos) break; line++; } return line; } public int getColumnNumber(int pos) { // column number is 1-based return pos - m_lineOffsets.get(getLineNumber(pos) - 1) + 1; } private int toPos(int line, int col) { // line and col are 1-based return m_lineOffsets.get(line - 1) + col - 1; } }